﻿//-----------------------------------------------------------------------
// <copyright file="Generator.cs">
//     Copyright (c) 2010 Charles Young.
// </copyright>
// <summary>This is the Generator class.</summary>
//-----------------------------------------------------------------------

using System.Text;

namespace Tulip.Rules.CodeGen
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;
    using System.Reflection.Emit;
    using Tulip.Engine.Models.ClosedWorld;
    using Tulip.Engine;
    using Models.ClosedWorld;
    using Rules;

    /// <summary>
    /// Code generator class for Tuple List Processor models.
    /// </summary>
    public class Generator
    {
        
        /// <summary>
        /// A multicast delegate of tuple type generators.
        /// </summary>
        private TupleTypeGenerator tupleTypeGenerators;

        /// <summary>
        /// A multicast delegate of monadic function generators.
        /// </summary>
        private MonadicFunctionGenerator monadicFunctionGenerators;

        /// <summary>
        /// Defines a tuple type source generator delegate.
        /// </summary>
        /// <param name="classDef">A class definition.</param>
        public delegate StringBuilder TupleTypeGenerator(ClassDef classDef);

        /// <summary>
        /// Defines a monadic function generator delegate.
        /// </summary>
        /// <param name="execModule">A Reflection.Emit module builder.</param>
        public delegate void MonadicFunctionGenerator(ModuleBuilder execModule);

        /// <summary>
        /// Converts a free-form name to a type name.
        /// </summary>
        /// <param name="inName">A free-form name.</param>
        /// <returns>A programmatic type name based on the free-form name.</returns>
        public static string ConvertToTypeName(string inName)
        {
            // TODO:  Create better functionality to check for non-alpha start letters, etc.
            return inName.Trim().Replace(' ', '_');
        }

        /// <summary>
        /// Registers a tuple type generator with the generator object.
        /// </summary>
        /// <param name="tupleTypeGenerator">A tuple type generator delegate.</param>
        public void RegisterTupleTypeGenerator(TupleTypeGenerator tupleTypeGenerator)
        {
            if (tupleTypeGenerator != null)
            {
                this.tupleTypeGenerators += tupleTypeGenerator;
            }
        }

        /// <summary>
        /// Registers a monadic function generator with the genrator object.
        /// </summary>
        /// <param name="monadicFunctionGenerator">A monadic function generator delegate.</param>
        public void RegisterMonadicFunctionGenerator(MonadicFunctionGenerator monadicFunctionGenerator)
        {
            if (monadicFunctionGenerator != null)
            {
                this.monadicFunctionGenerators += monadicFunctionGenerator;
            }
        }

        /// <summary>
        /// Generates an executor.   An executor object is a dynamically constructed implementation 
        /// of a Tuple List monad containing functional methods and state.
        /// </summary>
        /// <param name="model">Top level node of a rule model.</param>
        /// <param name="modelTemplates">A set of model implementors.</param>
        /// <returns>A dynamically constructed executor object.</returns>
        public Executor GenerateExecutor(Items model, ModelTemplates modelTemplates)
        {
            if (model != null)
            {
                modelTemplates.EntityTupleTemplate.RegisterWithGenerator(this);

                var output = new StringBuilder();
                output.Append("namespace Tulip.Rules.Codegen\r\n{\r\n    using Tulip.Rule\r\n");

                StringBuilder sb = null;
                var template = ((Templates.EntityTupleTemplate)modelTemplates.EntityTupleTemplate);

                foreach (var classDef in GetTypeData(model))
                {
                    sb = template.GetGeneratedSource(classDef);
                }

                output.Append(sb.ToString());
                output.Append("\r\n}");
                System.Diagnostics.Trace.WriteLine(output.ToString());

                //TODO:create the c# class in an assembly from the output, compile and then return the Executor

                return null;
            }
            else
            {
                throw new ArgumentException("No model passed to implementor", "model");
            }
        }

        /////////// <summary>
        /////////// 
        /////////// </summary>
        ////////public void SetTypeCreateor()
        ////////{
        ////////}

        /// <summary>
        /// Creates a dynamic assembly.
        /// </summary>
        /// <param name="model">Top level node of a rule model.</param>
        /// <returns>A dynamic assembly.</returns>
        public Assembly CreateDynamicAssembly(Items model)
        {
            return this.CreateDynamicAssembly(1, model, null, null);
        }

        /// <summary>
        /// Creates a dynamic assembly.
        /// </summary>
        /// <param name="model">Top level node of a rule model.</param>
        /// <param name="assemblyAttributes">Collection of custom attribute builders.</param>
        /// <returns>A dynamic assembly.</returns>
        public Assembly CreateDynamicAssembly(Items model, IEnumerable<CustomAttributeBuilder> assemblyAttributes)
        {
            return this.CreateDynamicAssembly(2, model, null, assemblyAttributes);
        }

        /// <summary>
        /// Creates and saves a dynamic assembly to the current directory.
        /// </summary>
        /// <param name="model">Top level node of a rule model.</param>
        /// <param name="saveOnly">Indicates if the assembly can be saved only, and not executed.</param>
        /// <returns>A dynamic assembly.</returns>
        public Assembly CreateAndSaveDynamicAssembly(Items model, bool saveOnly)
        {
            if (saveOnly)
            {
                return this.CreateDynamicAssembly(5, model, System.IO.Directory.GetCurrentDirectory(), null);
            }
            else
            {
                return this.CreateDynamicAssembly(3, model, System.IO.Directory.GetCurrentDirectory(), null);
            }
        }

        /// <summary>
        /// Creates and saves a dynamic assembly to the given directory.
        /// </summary>
        /// <param name="model">Top level node of a rule model.</param>
        /// <param name="dir">Directory path to which the assembly whould be saved.</param>
        /// <param name="saveOnly">Indicates if the assembly can be saved only, and not executed.</param>
        /// <returns>A dynamic assembly.</returns>
        public Assembly CreateAndSaveDynamicAssembly(Items model, string dir, bool saveOnly)
        {
            if (saveOnly)
            {
                return this.CreateDynamicAssembly(6, model, dir, null);
            }
            else
            {
                return this.CreateDynamicAssembly(4, model, dir, null);
            }
        }

        /// <summary>
        /// Queries the model to return a collection of class definitions.
        /// </summary>
        /// <param name="myModel">Top level node of a rule model.</param>
        /// <returns>A colelction of class definitions.</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Necessary complexity."),
         System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1505:AvoidUnmaintainableCode", Justification = "Necessary complexity."),
         System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Necessary complexity.")]
        private static IEnumerable<ClassDef> GetTypeData(Items myModel)
        {
            // Select data for class definitions
            return from tuple
                       in myModel.Tuples
                   join pr
                       in myModel.Productions
                       on tuple.Name equals pr.TupleRef
                       into tempPr
                   from pr in tempPr.DefaultIfEmpty()
                   select new ClassDef
                              {
                                  ClassName = tuple.Name,
                                  Role = tuple.Role,
                                  Facts = from frame
                                              in
                                              tuple.Attributes.GroupBy(attr => attr.Frame,
                                                                       (frame, att) => new { Name = frame })
                                          from classFrame
                                              in myModel.ClassFrames
                                          join type
                                              in myModel.Types
                                              on classFrame.BoundType equals type.Name
                                              into boundTypes
                                          from boundType in boundTypes.DefaultIfEmpty()
                                          from assemblyRef in myModel.AssemblyReferences
                                          where
                                              frame.Name == classFrame.Name && boundType.AssemblyRef == assemblyRef.Name
                                          select new FactDef
                                                     {
                                                         FactName = classFrame.Name,
                                                         TypeName = boundType == null ? string.Empty : boundType.Name,
                                                         TypeAssemblyName =
                                                             boundType == null ? string.Empty : assemblyRef.Name,
                                                         TypeAssemblyVersionMajor =
                                                             boundType == null ? 0 : assemblyRef.VersionMajor,
                                                         TypeAssemblyVersionMinor =
                                                             boundType == null ? 0 : assemblyRef.VersionMinor,
                                                         TypeAssemblyVersionRevision =
                                                             boundType == null ? 0 : assemblyRef.VersionRevision,
                                                         TypeAssemblyVersionBuild =
                                                             boundType == null ? 0 : assemblyRef.VersionBuild,
                                                         TypeAssemblyCulture =
                                                             boundType == null ? string.Empty : assemblyRef.Culture,
                                                         TypeAssemblyPublicKeyToken =
                                                             boundType == null
                                                                 ? string.Empty
                                                                 : assemblyRef.PublicKeyToken,
                                                         TypeKind =
                                                             boundType == null ? TypeKind.Unknown : boundType.TypeKind,
                                                         TypeVisibility =
                                                             boundType == null
                                                                 ? TypeVisibility.Unknown
                                                                 : boundType.TypeVisibility,
                                                         TypeIsStatic = boundType == null ? false : boundType.IsStatic,
                                                         TypeGenericTypeArgument =
                                                             boundType == null
                                                                 ? string.Empty
                                                                 : boundType.GenericTypeArgument
                                                     },
                                  Members = from attribute in tuple.Attributes
                                            from frame in myModel.ClassFrames 
                                            from slot in frame.Slots
                                            from boundType in myModel.Types
                                            from boundMember in boundType.Members
                                            from assemblyRef in myModel.AssemblyReferences
                                            where attribute.Frame == frame.Name &&
                                                  attribute.Slot == slot.Name &&
                                                  frame.BoundType == boundType.Name &&
                                                  slot.BoundMember == boundMember.Name &&
                                                  boundType.AssemblyRef == assemblyRef.Name
                                            select new MemberDef
                                            {
                                                Name = attribute.Name,
                                                IsCached = attribute.Cache,
                                                FactName = attribute.Frame,
                                                BoundMemberTypeAssemblyName = boundType == null ? string.Empty : assemblyRef.Name + ", Version=" +
                                                      ((assemblyRef.VersionMajor == 0) ? string.Empty : assemblyRef.VersionMajor.ToString() + "." +
                                                                                   assemblyRef.VersionMinor + "." +
                                                                                   assemblyRef.VersionRevision + "." +
                                                                                   assemblyRef.VersionBuild +
                                                      (String.IsNullOrEmpty(assemblyRef.Culture) ? string.Empty : ", Culture=" + assemblyRef.Culture) +
                                                      (String.IsNullOrEmpty(assemblyRef.PublicKeyToken) ? string.Empty : ", PublicKeyToken=" + assemblyRef.PublicKeyToken)),
                                                BoundMemberTypeName = boundType.Name,
                                                BoundMemberName = boundMember.Name,
                                                BoundMemberKind = boundMember.Kind,
                                                BoundMemberVisibility = boundMember.Visibility,
                                                BoundMemberIsStatic = boundMember.IsStatic,
                                                Parameters = from parameter
                                                             in boundMember.Parameters
                                                             join type
                                                                in myModel.Types
                                                             on parameter.TypeRef equals type.Name
                                                             into types
                                                             from type in types.DefaultIfEmpty()
                                                             where !type.IsStatic  // Tools should never allow static parameter definitions
                                                             select new ParameterDef
                                                             {
                                                                 Name = parameter.Name,
                                                                 ByRef = parameter.IsByRef,
                                                                 Out = parameter.IsOut,
                                                                 Return = parameter.IsReturn,
                                                                 Params = parameter.IsParams,
                                                                 TypeName = type == null ? string.Empty : type.Name,
                                                                 TypeAssembly = type == null ? string.Empty : type.AssemblyRef,
                                                                 TypeKind = type == null ? TypeKind.Unknown : type.TypeKind,
                                                                 TypeVisibility = type == null ? TypeVisibility.Unknown : type.TypeVisibility,
                                                                 //TypeIsStatic = mt == null ? false : mt.IsStatic,
                                                                 TypeGenericTypeArgument = type == null ? string.Empty : type.GenericTypeArgument
                                                             },
                                            },
                                  Relations = from r
                                              in myModel.Relations
                                              where tuple.Name == r.TupleRef
                                              select new RelationDef
                                              {
                                                  Id = r.Id,
                                                  Name = r.Name,
                                                  TupleRef = r.TupleRef,
                                                  Indexes = from idx
                                                            in r.Indexes
                                                            select new IndexDef
                                                            {
                                                                Id = idx.Id,
                                                                Name = idx.Name,
                                                                Fields = from fld
                                                                         in idx.Fields
                                                                         select new FieldDef
                                                                         {
                                                                             Index = fld.Index,
                                                                             Frame = fld.Frame,
                                                                             SlotRef = fld.SlotRef
                                                                         }
                                                            }
                                              },
                                  ProductionSalience = pr == null ? 0 : pr.Salience,
                                  ProductionPosition = pr == null ? 0 : pr.Position,
                                  ProductionSpecificity = pr == null ? 0 : pr.Specificity,
                                  ProductionUiActionText = pr == null ? string.Empty : pr.ActionText,
                                  ProductionExpressions = pr == null ? null : from ex in pr.Expressions
                                                                              select new ExpressionDef
                                                                              {
                                                                                  ExpressionName = ex.Name,
                                                                                  ExpressionPosition = ex.Position,
                                                                                  ExpressionText = ex.Text,
                                                                                  ExpressionArguments = from ag in ex.Arguments
                                                                                                        select new ArgumentDef
                                                                                                        {
                                                                                                            ArgumentName = ag.Name,
                                                                                                            ArgumentPosition = ag.Position,
                                                                                                            ArgumentExpression = new ExpressionDef
                                                                                                            {
                                                                                                                ExpressionName = ag.Expression.Name,
                                                                                                                ExpressionPosition = ag.Expression.Position,
                                                                                                                ExpressionText = ag.Expression.Text
                                                                                                            }
                                                                                                        }
                                                                              }
                              };

            // return null;
        }

        /// <summary>
        /// Creates a dynamic assembly.
        /// </summary>
        /// <param name="variant">Indicates which variant of synamic assembly creation should be used. 
        /// 1=run, 2=run with custom attributes, 3=run and save locally, 4=run and save to given directory, 
        /// 5=save only locally, 6=save only to given directory.</param>
        /// <param name="model">Top level node of a rule model.</param>
        /// <param name="dir">Directory to which the assembly will be save (variants 4 and 6).</param>
        /// <param name="assemblyAttributes">Collection of custom assembly attributes.</param>
        /// <returns>A dynamic assembly.</returns>
        private Assembly CreateDynamicAssembly(int variant, Items model, string dir, IEnumerable<CustomAttributeBuilder> assemblyAttributes)
        {
            if (model != null)
            {
                var asmName = ConvertToTypeName(model.Name);
                var currentDomain = AppDomain.CurrentDomain;
                var execAsmName = new AssemblyName(asmName);
                execAsmName.Version = model.Version;
                execAsmName.CultureInfo = model.CultureInfo;

                // TODO: Build in support for strong-names
                // execAsmName.KeyPair = myModel.StrongNameKeyPair;
                execAsmName.ProcessorArchitecture = model.ProcessorArchitecture;

                AssemblyBuilder dynamicAssembly;

                switch (variant)
                {
                    case 1:
                        dynamicAssembly = currentDomain.DefineDynamicAssembly(execAsmName, AssemblyBuilderAccess.Run);
                        break;
                    case 2:
                        dynamicAssembly = currentDomain.DefineDynamicAssembly(execAsmName, AssemblyBuilderAccess.Run, assemblyAttributes);
                        break;
                    case 3:
                        dynamicAssembly = currentDomain.DefineDynamicAssembly(execAsmName, AssemblyBuilderAccess.RunAndSave);
                        break;
                    case 4:
                        dynamicAssembly = currentDomain.DefineDynamicAssembly(execAsmName, AssemblyBuilderAccess.RunAndSave, dir);
                        break;
                    case 5:
                        dynamicAssembly = currentDomain.DefineDynamicAssembly(execAsmName, AssemblyBuilderAccess.Save);
                        break;
                    case 6:
                        dynamicAssembly = currentDomain.DefineDynamicAssembly(execAsmName, AssemblyBuilderAccess.Save, dir);
                        break;
                    default:
                        throw new ArgumentException("Invalid variant", "variant");
                }

                ModuleBuilder execModule;

                if (variant <= 2)
                {
                    execModule = dynamicAssembly.DefineDynamicModule(execAsmName.Name);
                }
                else
                {
                    execModule = dynamicAssembly.DefineDynamicModule(execAsmName.Name, execAsmName.Name + ".dll");
                }

                this.GenerateTupleTypes(model, execModule);

                if (variant > 2)
                {
                    dynamicAssembly.Save(dynamicAssembly.GetName().Name + ".dll");
                }

                return dynamicAssembly;
            }
            else
            {
                throw new ArgumentException("No model passed to implementor", "model");
            }
        }

        /// <summary>
        /// Generate the tuple types within the dynamic assembly.
        /// </summary>
        /// <param name="model">Top level node of a rule model.</param>
        /// <param name="execModule">A Reflection.Emit module builder.</param>
        private void GenerateTupleTypes(Items model, ModuleBuilder execModule)
        {
            // Select data for 'Entity' role tuples
            var classDefs = GetTypeData(model);

            if (classDefs == null)
            {
                return;
            }

            foreach (var classDef in classDefs)
            {
                var source = this.tupleTypeGenerators(classDef);
            }
        }

        ////////private System.Type GenerateTupleType(ModuleBuilder execModule, ClassDef classDef)
        ////////{
        ////////    switch (classDef.Role)
        ////////    {
        ////////        case TupleRole.Entity:
        ////////            return CreateEntityClass(execModule, classDef);
        ////////    }

        ////////    return default(System.Type);

        ////////    //if (generatedType != null)
        ////////    //{
        ////////    //    types.Add(generatedType);
        ////////    //}

        ////////}
    }
}
